package com.functionx.provider;

import com.functionx.contract.IAirQuality;
import com.functionx.dto.MonitoringDto;
import com.functionx.dto.AreaDto;
import com.functionx.utils.CalcUtil;
import com.functionx.utils.HttpUtil;
import com.functionx.utils.JsonUtil;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.*;

@Service
public class AirQualityService implements IAirQuality {

    private static final Logger logger = LoggerFactory.getLogger(AirQualityService.class);

    private static final Map<Integer, AreaDto<List<MonitoringDto>>> monitors = new ConcurrentHashMap<>();

    private static long stamp = new Date().getTime();

    private static final String aq_url_template = "https://api.waqi.info/mapq/bounds/?" +
            "bounds=%f,%f,%f,%f&inc=placeholders&k=_2Y2EvHxxYHV0cMw0JSzJWXmpjcA09LRlTFVYZIA==&_=1562393593323";

    private static final Double[] shanghai_bounds = new Double[]{30.40, 120.52, 31.53, 122.12};
    private static final Double[] beijing_bounds = new Double[]{39.65, 116.03, 40.189, 116.74};
    private static final Double[] nanjing_bounds = new Double[]{31.88, 118.56, 32.24, 118.98};
    private static final Double[] hangzhou_bounds = new Double[]{30.11, 119.85, 30.62, 120.44};
    private static final Double[] guangzhou_bounds = new Double[]{22.86, 112.97, 23.42, 113.57};
    private static final Double[] chengdu_bounds = new Double[]{30.41, 103.75, 30.94, 104.38};
    private static final Double[] changchun_bounds = new Double[]{43.63, 124.98, 44.11, 125.65};
    private static final Double[] jinan_bounds = new Double[]{36.42, 116.76, 36.89, 117.36};
    private static final Double[] jilin_bounds = new Double[]{43.66, 126.28, 44.04, 126.82};
    private static final Double[] jincheng_bounds = new Double[]{35.44, 112.78, 35.55, 112.91};
    private static final Double[] yinchuan_bounds = new Double[]{38.27, 105.91, 38.69, 106.44};
    private static final List<Double[]> city_bounds = new ArrayList<>();

    static {
        city_bounds.addAll(Arrays.asList(shanghai_bounds, beijing_bounds, nanjing_bounds, hangzhou_bounds,
                guangzhou_bounds, chengdu_bounds, changchun_bounds, jinan_bounds, jilin_bounds, jincheng_bounds, yinchuan_bounds));
    }

    @PostConstruct
    protected void initService() {
        Runnable runnable = () -> {
            boolean isFirst = true;
            while (true) {
                try {
                    if (isFirst)
                        isFirst = false;
                    else
                        TimeUnit.MINUTES.sleep(10);
                    fetchAllMonitors();
                } catch (Exception e) {
                    logger.warn(e.getMessage());
                }
            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(runnable);
        executor.shutdown();
    }

    @Override
    public int get(double gpsLongitude, double gpsLatitude) {
        int code = MonitoringDto.code(gpsLatitude, gpsLongitude);
        int latNum = code % 1000;
        int lonNum = code / 1000;
        List<MonitoringDto> monitoringDtos = new ArrayList<>();
        for (int i = latNum - 1; i <= latNum + 1; i++)
            for (int j = lonNum - 1; j <= lonNum; j++) {
                AreaDto<List<MonitoringDto>> areaDto = monitors.get(i + j * 1000);
                if(areaDto != null) {
                    List<MonitoringDto> monitorings = areaDto.getData();
                    if (monitorings != null)
                        monitoringDtos.addAll(monitorings);
                }
            }
        MonitoringDto closestMonitoring = selectClosestMonitoring(gpsLongitude, gpsLatitude, monitoringDtos);
        return getAqi(closestMonitoring);
    }

    @Override
    public Map<Integer, Integer> get(List<Object[]> positions) {
        Map<Integer, Integer> airQualities = new HashMap<>();
        for (Object[] position : positions) {
            int id = (int) position[0];
            double gpsLongitude = (double) position[1];
            double gpsLatitude = (double) position[2];
            int airQuality = get(gpsLongitude, gpsLatitude);
            airQualities.put(id, airQuality);
        }
        return airQualities;
    }

    @Override
    public int getRT(double gpsLongitude, double gpsLatitude) {
        Double[] bounds = caclBounds(gpsLongitude, gpsLatitude);
        String url = String.format(aq_url_template, bounds[1], bounds[0], bounds[3], bounds[2]);
        String response = HttpUtil.doGet(url);
        List<MonitoringDto> monitorings = JsonUtil.jsonToList(response, MonitoringDto.class);
        MonitoringDto closestMonitoring = selectClosestMonitoring(gpsLongitude, gpsLatitude, monitorings);
        return getAqi(closestMonitoring);
    }

    private int getAqi(MonitoringDto monitoringDto) {
        if (monitoringDto == null)
            return -1;
        else if (monitoringDto.getDistance() > 20_000)
            return -2;
        else if (new Date().getTime() / 1000 - monitoringDto.getStamp() >= 6 * 60 * 60)
            return -3;
        else
            return monitoringDto.getAqi();
    }

    private Double[] caclBounds(double gpsLongitude, double gpsLatitude) {
        Double[] bounds = new Double[4];
        bounds[0] = gpsLongitude - 0.0527;
        bounds[1] = gpsLatitude - 0.0445;
        bounds[2] = gpsLongitude + 0.0527;
        bounds[3] = gpsLatitude + 0.0445;

        return bounds;
    }

    private MonitoringDto selectClosestMonitoring(double gpsLongitude, double gpsLatitude, List<MonitoringDto> monitorings) {
        MonitoringDto closestMonitoring = null;
        double closestDistance = 0;
        while (true) {
            Iterator<MonitoringDto> iterator = monitorings.iterator();
            while (iterator.hasNext()) {
                MonitoringDto monitoring = iterator.next();
                double distance = CalcUtil.getDistance(gpsLatitude, gpsLongitude, monitoring.getLat(), monitoring.getLon());
                monitoring.setDistance(distance);
                if (closestDistance == 0) {
                    closestDistance = distance;
                    closestMonitoring = monitoring;
                } else if (distance < closestDistance) {
                    closestDistance = distance;
                    closestMonitoring = monitoring;
                }
            }
            if (closestMonitoring == null)
                return null;
            else if (new Date().getTime() / 1000 - closestMonitoring.getStamp() >= 6 * 60 * 60)
                if (monitorings.size() > 1)
                    iterator.remove();
                else
                    return closestMonitoring;
            else
                return closestMonitoring;
        }
    }

    private void fetchAllMonitors() throws InterruptedException {
        stamp = System.currentTimeMillis();
        for (Double[] bounds : city_bounds) {
            String url = String.format(aq_url_template, bounds);
            String response = HttpUtil.doGet(url);
            List<MonitoringDto> monitorings = JsonUtil.jsonToList(response, MonitoringDto.class);
            for (MonitoringDto monitoring : monitorings) {
                int code = monitoring.hashCode();
                AreaDto<List<MonitoringDto>> areaDto = monitors.get(code);
                if (areaDto == null) {
                    areaDto = new AreaDto(stamp, new CopyOnWriteArrayList<>());
                    monitors.put(code, areaDto);
                } else if (areaDto.getStamp() != stamp) {
                    areaDto.setStamp(stamp);
                    areaDto.setData(new CopyOnWriteArrayList<>());
                }
                areaDto.getData().add(monitoring);
            }
            TimeUnit.SECONDS.sleep(10);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IAirQuality airQuality = new AirQualityService();
        ((AirQualityService) airQuality).fetchAllMonitors();
        System.out.println(airQuality.get(126.549896, 43.813152));
    }
}
